import re
import subprocess
from threading import Thread, Lock

import numpy as np
import paramiko
import pysftp
from dateutil.tz import tz
import io_util
import os
import time
import datetime
import logging
import random
import hashlib
import base64

from dtos.result import Result
from libs.redis_wrapper import RedisWrapper
from config import HOST,USERNAME,PASSWORD


# import dateutil.tz as tz


class CommonUtil(object):

    @staticmethod
    def get_md5_by_str(string):
        data_md5 = hashlib.md5(base64.b64decode(string)).hexdigest()
        return data_md5

    @staticmethod
    def get_sign(origin, retouch, timestamp, api_secret):
        sign = hashlib.md5(
            (api_secret + origin[0]['filename'] + retouch[0]['filename'] + str(timestamp)).encode()).hexdigest().upper()
        print(sign)
        return sign

    @staticmethod
    def calculate_file_hash(file_path):
        """计算文件的 MD5 哈希值"""
        hash_md5 = hashlib.md5()
        with open(file_path, "rb") as f:
            for chunk in iter(lambda: f.read(), b""):
                hash_md5.update(chunk)
        return hash_md5.hexdigest()

    @staticmethod
    def open_file(path):
        try:
            with open(path, 'rb') as f:
                res = f.read()
        except Exception as ex:
            logging.info("打开文件：%s出现异常，原因：%s" % (path, str(ex)))
            res = None
        return res

    @staticmethod
    def compare_list(new_list, exist_list):
        """
        在m2m关系中，计算需要新增和删除的数据
        """
        add_list, delete_list = [], []
        for item in new_list:
            if item not in exist_list:
                add_list.append(item)
        for item in exist_list:
            if item not in new_list:
                delete_list.append(item)
        return add_list, delete_list

    @staticmethod
    def get_bytes_io_by_file_obj(file_obj):
        """
        file对象转二进制io流文件对象
        """
        bytes_io = io_util.BytesIO()
        bytes_io.write(file_obj)
        return bytes_io

    @staticmethod
    def get_md5_value(string):
        try:
            return hashlib.md5(string.encode()).hexdigest()
        except Exception as ex:
            logging.info("md5时出现异常，原因：%s" % str(ex))
            return ""

    @staticmethod
    def get_current_year_month_day():
        year = datetime.datetime.now().year
        month = datetime.datetime.now().month
        day = datetime.datetime.now().day
        return year, month, day

    @staticmethod
    def get_path_by_system_version(path_list):
        if os.name == 'nt':
            return "\\".join(path_list)
        return "/".join(path_list)
    @staticmethod
    def convert_timestamp_to_dt(timestamp, f="%Y-%m-%d %H:%M:%S"):
        if not timestamp:
            return None
        try:
            dt = datetime.datetime.fromtimestamp(timestamp)
            # from_zone = tz.tzutc()
            to_zone = tz.tzlocal()
            # utc = dt.replace(tzinfo=from_zone)  # 先行替换datetime的时区到utc时区
            local = dt.astimezone(to_zone)
            return local.strftime(f)
        except Exception as ex:
            logging.info("转换时间戳到时间格式时出现错误，原因：%s" % str(ex))
            return None

    @staticmethod
    def get_timestamp_thirteen():
        return int(round(time.time() * 1000))

    @staticmethod
    def get_time_array_thirteen(timestamp):
        try:
            r = time.localtime(timestamp / 1000)
            return r
        except Exception as ex:
            logging.info("转换时间戳到时间格式时出现错误，原因：%s" % str(ex))
            return None

    @staticmethod
    def get_statistic_condition(conditions, field_list):
        statistic_condition = []
        for con in conditions:
            if con and con.get("field", None) not in field_list:
                statistic_condition.append(con)
        return statistic_condition

    @staticmethod
    def check_is_number(v):
        try:
            v = str(v)
            if not v:
                return v
            if v[0] == '-':
                v = v[1:]
            return v.isdigit()
        except Exception as ex:
            logging.info("check_is_number函数出现错误，原因：%s" % str(ex))
            return False

    @staticmethod
    def random_str(length=32):
        chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
        rand = random.Random()
        return "".join([chars[rand.randint(0, len(chars) - 1)] for i in range(length)])

    @staticmethod
    def random_digit(length=4):
        res = []
        for i in range(length):
            r = random.randint(0, 9)
            res.append(str(r))
        return "".join(res)

    @staticmethod
    def private_phone(phone):
        if not phone:
            return None
        phone = str(phone)
        if len(phone) != 11:
            return None
        return "%s****%s" % (phone[:3], phone[7:])

    @staticmethod
    def composetime(nyrsfm_time):
        time1 = datetime.datetime.strptime(nyrsfm_time, "%Y-%m-%d %H:%M:%S")
        time2 = int(time.mktime(time1.timetuple()))
        return time2

    @staticmethod
    def _replace_digit(d):
        if not d:
            return d
        if len(str(d)) == 1:
            return "0%s" % d
        return d

    @staticmethod
    def get_out_trade_no(data_id, prefix="KS"):
        timestamp = CommonUtil.get_timestamp_thirteen()
        r = CommonUtil.get_time_array_thirteen(int(timestamp))
        year, month, day, hour, m, s = r.tm_year, r.tm_mon, r.tm_mday, r.tm_hour, r.tm_min, r.tm_sec
        _f = CommonUtil._replace_digit
        out_trade_no = "%s%s%s%s%s%s%s%s" % (prefix, year, _f(month), _f(day), _f(hour), _f(m), _f(s), data_id)
        return out_trade_no

    @staticmethod
    def get_timestamp():
        return int(datetime.datetime.now().timestamp())

    # 获取今日0点 和 12:59:59 和昨天0点
    @staticmethod
    def get_yesterday_time(yester_day=1):
        # 获取当前时间
        now = datetime.datetime.now()
        # 获取今天零点
        zeroToday = now - datetime.timedelta(hours=now.hour, minutes=now.minute, seconds=now.second,
                                             microseconds=now.microsecond)
        # 获取23:59:59
        lastToday = zeroToday + datetime.timedelta(hours=23, minutes=59, seconds=59)
        lastToday = int(time.mktime(lastToday.timetuple()))
        zeroToday = int(time.mktime(zeroToday.timetuple()))

        today = datetime.date.today()
        yesterday = today - datetime.timedelta(days=yester_day)
        yesterday_start_time = int(time.mktime(time.strptime(str(yesterday), '%Y-%m-%d')))
        return now, zeroToday, lastToday, yesterday_start_time

    # 增加时间/过了几天
    @staticmethod
    def add_time(day=1):
        current_datetime = datetime.datetime.now()
        # 加天数
        time_res = current_datetime + datetime.timedelta(days=day)
        timestamp = int(time.mktime(time_res.timetuple()))
        return timestamp

    # 计算时间戳差异天数
    @staticmethod
    def get_time_day(now_time, end_time):

        # 将时间戳转换为datetime对象
        date1 = datetime.datetime.fromtimestamp(now_time)
        date2 = datetime.datetime.fromtimestamp(end_time)
        # 计算日期差异
        difference = date2 - date1
        # 将日期差异转换为总秒数
        total_seconds = difference.total_seconds()
        # 提取整天数、剩余小时数和剩余分钟数
        days = int(total_seconds // (24 * 3600))
        remaining_seconds = total_seconds % (24 * 3600)
        hours = int(remaining_seconds // 3600)
        remaining_seconds %= 3600
        minutes = int(remaining_seconds // 60)

        return days, hours, minutes
    @staticmethod
    def camel2snake(string):
        """
        convert camel case to snake case
        """
        if string is None:
            return None
        new_string = ""
        for c in string:
            if c >= "A" and c <= "Z":
                new_string += "_" + c.lower()
            else:
                new_string += c
        return new_string

    @staticmethod
    def rand_hex_color(current_colors=[]):
        random.seed(time.time())
        r = lambda: random.randint(0, 255)
        while True:
            c = "#%02X%02X%02X" % (r(), r(), r())
            c = c.upper()
            if c not in current_colors:
                break
        return c
    @staticmethod
    def unique_within_project(labels, new_labels=[], col_names=["id", "name"]):
        rets = []
        for column_idx, column_name in enumerate(col_names):
            results = [True] * len(new_labels)
            curr_values = set()
            for label in labels:
                curr_values.add(getattr(label, column_name))
            new_values = set()
            for idx, new_label in enumerate(new_labels):
                new_value = getattr(new_label, column_name)
                if new_value in curr_values:
                    results[idx] = False
                    continue
                if new_value in new_values:
                    results[idx] = False
                    continue
                new_values.add(new_value)
            rets.append(results)
        rets = np.array(rets)
        unique = np.all(rets, axis=0)

        if len(rets) == 1:
            return rets[0], unique[0]
        return rets, unique
    @staticmethod
    def is_dir(sftp: pysftp.Connection, path: str) -> bool:
        try:
            return sftp.isdir(path)
        except Exception as e:
            print(f'is_dir exception: {e}')
            return False

    @staticmethod
    def is_file(sftp: pysftp.Connection, path: str) -> bool:
        try:
            return sftp.isfile(path)
        except Exception as e:
            print(f'is_file exception: {e}')
            return False


    @staticmethod
    def set_progress(user_id, task_id, zip_file, value, ttl=None):
        """设置进度值，并可选设置过期时间"""
        key = f'progress:{user_id}:{task_id}:{zip_file}'
        RedisWrapper.set(key, value, ex=ttl)

    @staticmethod
    def get_progress(user_id, task_id, zip_file):
        """获取进度值"""
        key = f'progress:{user_id}:{task_id}:{zip_file}'
        value = RedisWrapper.get(key)
        return float(value) if value else 0.0

    @staticmethod
    def zip_dir(user_id, task_id, remote_dir, local_zip_file):
        """在远程服务器上压缩目录，并下载压缩包到本地"""
        remote_zip_file_name = os.path.basename(local_zip_file)
        remote_zip_dir = os.path.dirname(remote_dir)

        # 创建SSH客户端
        ssh = paramiko.SSHClient()
        # 自动添加未知的服务器密钥及策略
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        try:
            ssh.connect(HOST, port=22, username=USERNAME, password=PASSWORD)
        except (paramiko.AuthenticationException, paramiko.SSHException, paramiko.BadHostKeyException) as e:
            logging.error(f"SSH连接失败: {e}")
            CommonUtil.set_progress(user_id, task_id, local_zip_file, -1, ttl=86400)
            return False

        # 检查远程目录是否存在
        check_dir_command = f"if [ -d '{remote_dir}' ]; then echo 'exists'; else echo 'not exists'; fi"
        try:
            stdin, stdout, stderr = ssh.exec_command(check_dir_command)
            dir_exists = stdout.read().decode().strip()
        except Exception as e:
            logging.error(f"执行远程目录检查命令失败: {e}")
            CommonUtil.set_progress(user_id, task_id, local_zip_file, -1, ttl=86400)
            ssh.close()
            return False

        if dir_exists != 'exists':
            logging.error(f"远程目录 {remote_dir} 不存在")
            CommonUtil.set_progress(user_id, task_id, local_zip_file, -1, ttl=86400)
            ssh.close()
            return False

        # 设置初始进度
        CommonUtil.set_progress(user_id, task_id, local_zip_file, 0, ttl=86400)

        # 检查并安装 7za
        check_7z_command = "which 7za"
        try:
            stdin, stdout, stderr = ssh.exec_command(check_7z_command)
            if not stdout.read().decode().strip():
                install_7z_command = "yum install -y p7zip"
                stdin, stdout, stderr = ssh.exec_command(install_7z_command)
                exit_status = stdout.channel.recv_exit_status()
                if exit_status != 0:
                    logging.error(f"安装 7za 失败，退出状态码: {exit_status}")
                    CommonUtil.set_progress(user_id, task_id, local_zip_file, -1, ttl=86400)
                    ssh.close()
                    return False
        except Exception as e:
            logging.error(f"检查或安装 7za 失败: {e}")
            CommonUtil.set_progress(user_id, task_id, local_zip_file, -1, ttl=86400)
            ssh.close()
            return False

        # 创建远程压缩命令
        sevenz_command = f"cd {remote_dir} && 7za a -tzip {remote_zip_file_name} ."


        # 执行远程压缩命令
        try:
            stdin, stdout, stderr = ssh.exec_command(sevenz_command)
            logging.info("开始压缩文件为 .zip")
            exit_status = stdout.channel.recv_exit_status()
            if exit_status != 0:
                logging.error(f"远程压缩失败，退出状态码: {exit_status}")
                CommonUtil.set_progress(user_id, task_id, local_zip_file, -1, ttl=86400)
                ssh.close()
                return False
        except Exception as e:
            logging.error(f"执行远程压缩命令失败: {e}")
            CommonUtil.set_progress(user_id, task_id, local_zip_file, -1, ttl=86400)
            ssh.close()
            return False

        # 下载压缩包到本地
        try:
            sftp = ssh.open_sftp()
            remote_zip_path = f"{remote_dir}/{remote_zip_file_name}"  # 图片压缩包路径
            local_zip_path = local_zip_file  # 本地服务器上压缩包路径

            # 获取远程文件大小
            remote_file_size = sftp.stat(remote_zip_path).st_size
            downloaded_size = 0
            logging.info("开始下载")
            # 下载文件并更新进度
            with open(local_zip_path, 'wb') as local_file:
                remote_file = sftp.file(remote_zip_path, 'rb')
                buffer_size = 1024 * 1024 * 30  # 每次读取50MB
                while True:
                    data = remote_file.read(buffer_size)  # 每次读取1MB
                    if not data:
                        break
                    local_file.write(data)
                    downloaded_size += len(data)
                    progress = (downloaded_size / remote_file_size) * 100
                    if downloaded_size % (1024 * 1024 * 10) == 0:  # 每10MB更新一次进度
                        CommonUtil.set_progress(user_id, task_id, local_zip_file, progress, ttl=86400)
            remote_file.close()
            sftp.close()
        except Exception as e:
            logging.error(f"下载压缩包失败: {e}")
            CommonUtil.set_progress(user_id, task_id, local_zip_file, -1, ttl=86400)
            ssh.close()
            return False

        # 下载完成后设置进度为 100%
        CommonUtil.set_progress(user_id, task_id, local_zip_file, 100, ttl=86400)

        ssh.close()
        return True